package com.region.http.client.process;

import com.region.common.adapter.logging.LoggerAdapter;
import com.region.common.adapter.logging.LoggerAdapterFactory;
import com.region.common.utils.ReflectUtils;
import com.region.common.utils.StringUtils;
import com.region.http.client.common.RegionContextHolder;
import com.region.http.client.core.ClientProxyHandler;
import com.region.http.client.core.ProtectorMechanism;
import com.region.http.client.exception.NotFoundResultResolverException;
import com.region.http.client.model.RequestHeader;
import com.region.http.client.model.RequestMeta;
import com.region.http.client.model.RequestMeta.ConstParameter;
import com.region.http.client.model.RequestMeta.MetaParameter;
import com.region.http.client.model.RequestParameter;
import com.region.http.client.model.RequestParameter.RequestParameterBuilder;
import com.region.http.client.model.ResponseData;
import com.region.http.client.request.HttpExecutor;
import com.region.http.client.resolver.parameter.ParameterResolver;
import com.region.http.client.resolver.result.ResultResolver;

import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;

/**
 * The abstract http executor process
 *
 * @author liujieyu
 * @date 2023/7/8 22:32
 * @description
 */
public abstract class AbstractExecutorProcess implements ExecutorProcess {

    protected final LoggerAdapter loggerAdapter = LoggerAdapterFactory.getLogger(getClass());

    protected ClientProxyHandler handler;

    private List<ParameterResolver> parameterResolvers;

    private List<ResultResolver> resultResolvers;

    private ProtectorMechanism protectorMechanism;

    public AbstractExecutorProcess(List<ParameterResolver> parameterResolvers, List<ResultResolver> resultResolvers, ClientProxyHandler handler, ProtectorMechanism protectorMechanism) {
        this.parameterResolvers = parameterResolvers;
        this.resultResolvers = resultResolvers;
        this.handler = handler;
        this.protectorMechanism = protectorMechanism;
    }

    /**
     * Run parameter parser
     *
     * @param args
     * @throws Throwable
     */
    protected void runParameterResolver(Object[] args) throws Throwable {
        if (args != null) {
            for (int i = 0; i < args.length; i++) {
                Object arg = args[i];
                if (loggerAdapter.isDebugEnabled()) {
                    loggerAdapter.debug("Resolver parameter is " + arg);
                }
                for (ParameterResolver parameterResolver : parameterResolvers) {
                    arg = parameterResolver.resolver(arg);
                }
                args[i] = arg;
            }
        } else {
            if (loggerAdapter.isDebugEnabled()) {
                loggerAdapter.debug("The args parameter is null");
            }
        }
    }

    @Override
    public Object doExecute(RequestMeta meta, Object[] args, HttpExecutor executor) throws Throwable {
        try {
            if (protectorMechanism.isTrigger(meta)) {
                return protectorMechanism.invokeProtectorMethod(meta, args);
            } else {
                runParameterResolver(args);

                RequestParameter parameter = getParameter(meta.getMetaParameter(), meta.getConstParameter(), args);
                long time = System.nanoTime();
                ResponseData responseData = execute(meta, executor, parameter);

                if (meta.getProtectorMeta() != null) {
                    // save second
                    protectorMechanism.saveResponseTime(meta, 1.0 * (System.nanoTime() - time) / (1000000));
                }
                return runResultResolver(meta, responseData);
            }
        } catch (Throwable throwable) {
            loggerAdapter.error("Failed executor request", throwable);
            throw throwable;
        }
    }

    /**
     * Execute specific business logic code
     *
     * @return
     * @throws UnsupportedOperationException
     */
    public abstract ResponseData execute(RequestMeta requestMeta, HttpExecutor executor, RequestParameter parameter) throws Throwable;


    /**
     * Execute the result parser
     *
     * @param responseData
     * @return Returns the parser result
     */
    protected Object runResultResolver(RequestMeta meta, ResponseData responseData) throws Throwable {
        Type returnType = meta.getReturnType();
        Class<?> returnClazz = ReflectUtils.transform(meta.getReturnType());
        if (Future.class.isAssignableFrom(returnClazz) || CompletableFuture.class.isAssignableFrom(returnClazz)) {
            Type[] actuatorTypeArgs = ReflectUtils.getActuatorTypeArgs(returnType);
            if (actuatorTypeArgs != null && actuatorTypeArgs.length != 0) {
                returnType = actuatorTypeArgs[0];
            } else {
                // generic object
                returnType = Object.class;
            }
        }
        for (ResultResolver resultResolver : resultResolvers) {
            if (resultResolver.isResolver(returnType)) {
                if (loggerAdapter.isDebugEnabled()) {
                    loggerAdapter.debug("[" + meta.getMethod() + "] The current parser that satisfies the result is " + resultResolver);
                }
                return resultResolver.resolver(responseData
                        .setType(returnType)
                        .setAnnotations(meta.getMethod().getAnnotations()));
            }
        }
        throw new NotFoundResultResolverException("[" + meta.getMethod() + "] Not found result resolver");
    }

    /**
     * Constructing a request parameter object
     *
     * @param metaParameter
     * @param constParameter
     * @param args
     * @return
     */
    protected RequestParameter getParameter(MetaParameter metaParameter, ConstParameter constParameter, Object[] args) {
        RequestParameterBuilder builder = RequestParameter.builder();
        // const
        if (constParameter != null) {
            builder.headers(constParameter.getConstHeader())
                    .form(constParameter.getConstParameter())
                    .body(constParameter.getConstBody());
        }
        // overlay parameter
        if (metaParameter != null) {
            builder.body(metaParameter.getBodyIndex(), args)
                    .form(metaParameter.getKeyIndex(), args)
                    .headers(metaParameter.getHeaderIndex(), args);
        }
        // Loading Request Context Holder header information
        RequestHeader header = RegionContextHolder.getHeader();
        if (header != null) {
            builder.headers(header.get());
            //Remove Region Context data
            RegionContextHolder.remove();
        }
        return builder.build();
    }

    /**
     *
     * @param url
     * @param requestMeta
     * @param args
     * @return
     */
    protected String wrappingUrl(String url, RequestMeta requestMeta, Object[] args) {
        MetaParameter metaParameter = requestMeta.getMetaParameter();
        ConstParameter constParameter = requestMeta.getConstParameter();
        if (metaParameter == null && constParameter == null) {
            return url;
        }
        if (metaParameter != null) {
            Map<String, Integer> pathIndex = metaParameter.getPathIndex();
            // Encapsulation path parameter substitution
            if (pathIndex != null && !pathIndex.isEmpty()) {
                for (Map.Entry<String, Integer> entry : pathIndex.entrySet()) {
                    String pathKey = entry.getKey();
                    if (loggerAdapter.isDebugEnabled()) {
                        loggerAdapter.debug("[" + requestMeta.getMethod() + "] Parse path variable key is " + pathKey);
                    }
                    url = StringUtils.replace(url, "{" + pathKey + "}", String.valueOf(args[entry.getValue().intValue()]));
                }
            }
        }
        // const parameter
        if (constParameter != null && constParameter.getConstPath() != null) {
            Map<String, String> constPath = requestMeta.getConstParameter().getConstPath();
            for (Map.Entry<String, String> entry : constPath.entrySet()) {
                String pathKey = entry.getKey();
                String value = entry.getValue();
                if (loggerAdapter.isDebugEnabled()) {
                    loggerAdapter.debug("[" + requestMeta.getMethod() + "] Parse path variable key is " + pathKey);
                }
                url = StringUtils.replace(url, "{" + pathKey + "}", value);
            }
        }
        if (loggerAdapter.isDebugEnabled()) {
            loggerAdapter.debug("[" + requestMeta.getMethod() + "] After parsing url is " + url);
        }
        return url;
    }

    @Override
    public void close() {
        this.protectorMechanism.close();
    }
}
